package org.ponly.lpf4j.impl;

import org.ponly.lpf4j.ExtensionRegistry;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListSet;

/**
 */
public class DefaultExtensionRegistry implements ExtensionRegistry {
    private Map<Class<?>, ExtensionPoint<?>> extensionPointMap = new ConcurrentHashMap<>();

    @Override
    public <E> void registerExtension(final Class<E> extensionPoint, final E extension) {
        getRequiredExtensionPoint(extensionPoint).registerExtension(extension);
    }

    @Override
    public <E> void registerExtension(Class<E> extensionPoint, E extension, int loadingOrder) {
        getRequiredExtensionPoint(extensionPoint).registerExtension(extension, loadingOrder);
    }

    @SuppressWarnings("unchecked")
    private <E> ExtensionPoint<E> getRequiredExtensionPoint(final Class<E> extensionPoint) {
        assertIsExtensionPoint(extensionPoint);
        ExtensionPoint<E> context = (ExtensionPoint<E>) extensionPointMap.get(extensionPoint);

        if (null != context) {
            return context;
        }
        context = new ExtensionPoint<E>(extensionPoint);
        extensionPointMap.put(extensionPoint, context);
        return context;
    }

    @SuppressWarnings("unchecked")
    private <E> ExtensionPoint<E> getExtensionPoint(final Class<E> extensionPoint) {
        assertIsExtensionPoint(extensionPoint);
        return (ExtensionPoint<E>) extensionPointMap.get(extensionPoint);
    }

    private void assertIsExtensionPoint(Class<?> extensionPointClass) {
        if (null == extensionPointClass/* || !extensionPointClass.isAnnotationPresent(org.ponly.plugin.v2.ExtensionPoint.class)*/) {
            throw new IllegalArgumentException("'" + extensionPointClass + "' is not a extension point");
        }
    }

    @Override
    public <E> void unregisterExtension(Class<E> extensionPoint, E extension) {
        ExtensionPoint<E> context = getExtensionPoint(extensionPoint);
        if (null != context) {
            context.unregisterExtension(extension);
        }
    }

    @Override
    public boolean hasAnyExtensions(Class<?> extensionPoint) {
        ExtensionPoint<?> context = getExtensionPoint(extensionPoint);
        return null != context && context.hasAnyExtensions();
    }

    @Override
    public <E> E getExtension(Class<E> extensionPoint) {
        ExtensionPoint<E> context = getExtensionPoint(extensionPoint);
        return null != context ? context.getExtension() : null;
    }

    @Override
    public <E> Collection<E> getExtensions(Class<E> extensionPoint) {
        ExtensionPoint<E> context = getExtensionPoint(extensionPoint);
        return null != context ? Collections.unmodifiableCollection(context.getExtensions()) : Collections.<E>emptyList();
    }

    public void destroy() {
        Iterator<Map.Entry<Class<?>, ExtensionPoint<?>>> it = extensionPointMap.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<Class<?>, ExtensionPoint<?>> next = it.next();
            ExtensionPoint<?> context = next.getValue();
            context.clear();
            destroy(context);
            it.remove();
        }
        // TODO
        extensionPointMap.clear();
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        destroy();
    }

    private static void init(Object o) {
//        if (o instanceof Lifecycle) {
//            ((Lifecycle) o).init();
//        }
    }

    private static void destroy(Object o) {
//        if (o instanceof Lifecycle) {
//            ((Lifecycle) o).destroy();
//        }
    }

    /**
     * 扩展点
     *
     * @param <E>
     */
    static class ExtensionPoint<E> {
        final Class<E> extensionClass;
        final ConcurrentHashMap<E, Integer> extensionsMap = new ConcurrentHashMap<E, Integer>();
        final ConcurrentSkipListSet<E> extensions = new ConcurrentSkipListSet<E>(getComparator(extensionsMap));

        ExtensionPoint(Class<E> extensionClass) {
            this.extensionClass = extensionClass;
        }

        Class<E> getExtensionClass() {
            return extensionClass;
        }

        boolean registerExtension(E extension) {
            return registerExtension(extension, 0);
        }

        boolean registerExtension(E extension, int loadingOrder) {
            extensionsMap.putIfAbsent(extension, loadingOrder);
            return extensions.add(extension);
        }

        boolean unregisterExtension(E extension) {
            extensionsMap.remove(extension);
            boolean unregistered = extensions.remove(extension);
            if (unregistered) {
                destroy(extension);
            }
            return unregistered;
        }

        boolean hasAnyExtensions() {
            return null != getExtension();
        }

        boolean contains(E extension) {
            return extensions.contains(extension);
        }

        E getExtension() {
            Iterator<E> it = getExtensions().iterator();
            return it.hasNext() ? it.next() : null;
        }

        Collection<E> getExtensions() {
            return Collections.unmodifiableCollection(extensions);
        }

        synchronized void clear() {
            for (Iterator<E> it = extensions.iterator(); it.hasNext(); ) {
                E next = it.next();
                extensionsMap.remove(next);
                destroy(next);
            }
            extensions.clear();
            extensionsMap.clear();
        }

        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            clear();
        }
    }

    static <E> Comparator<E> getComparator(final Map<E, Integer> toIndexMap) {
        return new Comparator<E>() {

            @Override
            public int compare(E e1, E e2) {
                if (e1 == e2) {
                    return 0;
                }
                if (null == e1) {
                    return -1;
                }
                if (null == e2) {
                    return 1;
                }
                if (e1.equals(e2)) {
                    return 0;
                }

                Integer o1 = toIndexMap.get(e1);
                Integer o2 = toIndexMap.get(e2);
                o1 = null != o1 ? o1 : 0;
                o2 = null != o2 ? o2 : 0;
                int ret = Integer.compare(o1, o2);
                return 0 != ret ? ret : 1;
            }
        };
    }
}
